package model;

import java.awt.Graphics;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import model.behaviors.Bishop;
import model.behaviors.Horse;
import model.behaviors.King;
import model.behaviors.Pawn;
import model.behaviors.Queen;
import model.behaviors.Tower;
import model.viewModels.CenterModel;
import control.Communication;

/**
 * this class represents a match
 */
public class Match implements Positions {

    /**
     * match id
     */
    private int id;

    /**
     * first player
     */
    private User playerOne;

    /**
     * second player
     */
    private User playerTwo;

    /**
     * winner
     */
    private User winner;

    /**
     * start date
     */
    private String startDate;

    /**
     * count of moves
     */
    private int moveCount;

    /**
     * signals a remis
     */
    private boolean remis;

    /**
     * signals a patt
     */
    private boolean patt;

    /**
     * the list of moves in this match, descending ordered
     */
    private List<Move> movesList = new ArrayList<Move>();

    /**
     * the map of unbeaten figures in this match, use a constant from positions for the key string
     */
    private HashMap<String, Figure> figuresMap = new HashMap<String, Figure>();

    /**
     * a String of the selectedPosition
     */
    private String selectedPosition = "";

    /**
     * the selected move of the moves list
     */
    private int selectedMove = -1;

    /**
     * creates a new match-object, depending on the length of the matchData it will be an actMatch or an archMatch
     * 
     * @param matchData
     *            the data of the match
     */
    public Match( String[] matchData )
    {
	if ( matchData.length == 5 )
	{
	    createActMatch( matchData );
	}
	else if ( matchData.length == 8 )
	{
	    createArchMatch( matchData );
	}
	else
	{
	    System.err.println( "Matchdaten fehlerhaft: " + matchData.length );
	}

	initFiguresMap();
    }

    /**
     * creates a new actmatch out of the matchData
     * 
     * @param matchData
     *            contains id, player names,start date, move count
     */
    private void createActMatch( String[] matchData )
    {
	this.id = Integer.valueOf( matchData[0] );
	// is logged in user player one or two
	if ( matchData[1].equalsIgnoreCase( CenterModel.loggedInUser.getNick() ) )
	{
	    this.playerOne = CenterModel.loggedInUser;
	    this.playerTwo = new User( matchData[2] );
	}
	else
	{
	    this.playerOne = new User( matchData[1] );
	    this.playerTwo = CenterModel.loggedInUser;
	}

	this.startDate = matchData[3];
	this.moveCount = Integer.valueOf( matchData[4] );

	this.winner = null;
	this.remis = false;
	this.patt = false;

	initFiguresMap();
    }

    /**
     * creates a new archmatch out of the matchData
     * 
     * @param matchData
     *            contains id, player names,winner name,start date, move count,remis, patt
     */
    private void createArchMatch( String[] matchData )
    {
	this.id = Integer.valueOf( matchData[0] );
	// is logged in user player one or two
	if ( matchData[1].equalsIgnoreCase( CenterModel.loggedInUser.getNick() ) )
	{
	    this.playerOne = CenterModel.loggedInUser;
	    this.playerTwo = new User( matchData[2] );
	}
	else
	{
	    this.playerOne = new User( matchData[1] );
	    this.playerTwo = CenterModel.loggedInUser;
	}

	// winner
	if ( matchData[3].equalsIgnoreCase( this.playerOne.getNick() ) )
	{
	    this.winner = this.playerOne;
	}
	else if ( matchData[3].equalsIgnoreCase( this.playerTwo.getNick() ) )
	{
	    this.winner = this.playerTwo;
	}
	// if neither player one nor player two is the winner then the match ended with remis or patt
	else
	{
	    this.winner = User.NO_USER;
	}

	this.startDate = matchData[4];
	this.moveCount = Integer.valueOf( matchData[5] );
	this.remis = matchData[6].equals( "1" );
	this.patt = matchData[7].equals( "1" );
    }

    /**
     * initializes the figures map, sets all figures to their start positions
     */
    private void initFiguresMap()
    {
	// black figures
	int c = Figure.BLACK;
	this.figuresMap.put( POS_A8, new Figure( POS_A8, c, new Tower( c ) ) );
	this.figuresMap.put( POS_B8, new Figure( POS_B8, c, new Horse( c ) ) );
	this.figuresMap.put( POS_C8, new Figure( POS_C8, c, new Bishop( c ) ) );
	this.figuresMap.put( POS_D8, new Figure( POS_D8, c, new Queen( c ) ) );
	this.figuresMap.put( POS_E8, new Figure( POS_E8, c, new King( c ) ) );
	this.figuresMap.put( POS_F8, new Figure( POS_F8, c, new Bishop( c ) ) );
	this.figuresMap.put( POS_G8, new Figure( POS_G8, c, new Horse( c ) ) );
	this.figuresMap.put( POS_H8, new Figure( POS_H8, c, new Tower( c ) ) );

	// black pawns
	String pos;
	for ( char i = 'A'; i < 'I'; i++ )
	{
	    pos = i + "7";
	    this.figuresMap.put( pos, new Figure( pos, c, new Pawn( c ) ) );
	}

	// white pawns
	c = Figure.WHITE;
	this.figuresMap.put( POS_A1, new Figure( POS_A1, c, new Tower( c ) ) );
	this.figuresMap.put( POS_B1, new Figure( POS_B1, c, new Horse( c ) ) );
	this.figuresMap.put( POS_C1, new Figure( POS_C1, c, new Bishop( c ) ) );
	this.figuresMap.put( POS_D1, new Figure( POS_D1, c, new Queen( c ) ) );
	this.figuresMap.put( POS_E1, new Figure( POS_E1, c, new King( c ) ) );
	this.figuresMap.put( POS_F1, new Figure( POS_F1, c, new Bishop( c ) ) );
	this.figuresMap.put( POS_G1, new Figure( POS_G1, c, new Horse( c ) ) );
	this.figuresMap.put( POS_H1, new Figure( POS_H1, c, new Tower( c ) ) );

	// white figures
	for ( char i = 'A'; i < 'I'; i++ )
	{
	    pos = i + "2";
	    this.figuresMap.put( pos, new Figure( pos, c, new Pawn( c ) ) );
	}
    }

    /**
     * reset figures to start positions
     */
    private void resetFigurePositions()
    {
	HashMap<String, Figure> temp = new HashMap<String, Figure>( this.figuresMap );
	this.figuresMap.clear();
	for ( Figure f : temp.values() )
	{
	    f.resetPosition();
	    f.setBeaten( false );
	    // check if a queen is a promoted pawn
	    if ( f.getBehav() instanceof Queen )
	    {
		String startPos = f.getStartPosition();
		if ( !( startPos.equals( POS_D1 ) || startPos.equals( POS_D8 ) ) )
		{
		    f.setBehav( new Pawn( f.getColor() ) );
		}
	    }
	    this.figuresMap.put( f.getPosition(), f );
	}
    }

    /**
     * handles a click on a position of the board
     * 
     * @param pos
     *            the clicked position as a String
     * @return boolean value, if an update is necessary
     */
    public final boolean positionClickedAction( String pos )
    {
	boolean ret = false;
	// check if a figure exists at this position
	if ( this.figuresMap.containsKey( pos ) )
	{
	    Figure f = this.figuresMap.get( pos );
	    // check if it is your turn (check if the last move was not executed by logged in user)
	    // and ( if the last move is selected or if there are no moves yet )
	    if ( !CenterModel.loggedInUser.getNick().equals( this.getActingPlayer( this.moveCount ) )
		    && ( this.selectedMove == 0 || this.moveCount == 0 ) )
	    {
		// check if the clicked figure is selected now or not
		if ( f.clicked( this ) )
		{
		    // check if there is an other figure selected and deselect it
		    resetFigureSelection();
		    this.selectedPosition = pos;
		}
		else
		{
		    if ( !this.selectedPosition.isEmpty() )
		    {
			ret = moveFigure( pos, true );
		    }
		    resetFigureSelection();
		}
	    }
	}
	else
	{
	    if ( !this.selectedPosition.isEmpty() )
	    {
		ret = moveFigure( pos, false );
	    }
	    resetFigureSelection();
	}

	return ret;
    }

    /**
     * handle the movement of a figure if a valid field was clicked
     * 
     * @param endField
     *            the field the figure have to move to
     * @param enemyClicked
     *            boolean values, true if an enemy figure was clicked, this figure will be beaten false if an empty
     *            field was clicked
     * @return true if a valid field was chosen as the end field
     */
    private boolean moveFigure( String endField, boolean enemyClicked )
    {
	boolean ret = false;
	String startField = this.selectedPosition;
	Figure figureToMove = this.figuresMap.get( startField );

	// check if the goal field is a valid field
	if ( figureToMove.getBehav().isValidField( endField ) )
	{

	    String name = CenterModel.loggedInUser.getNick();
	    Move mToSend = new Move();
	    mToSend.setNumber( this.moveCount + 1 );
	    mToSend.setStartField( startField );
	    mToSend.setEndField( endField );
	    mToSend.setCastling( false );
	    mToSend.setChecked( false );
	    mToSend.setCheckmate( false );
	    mToSend.setEnPassant( false );
	    Communication.sendMove( name, mToSend, this.id );
	    // TODO Schachstellungen checken
	    ret = true;
	}

	return ret;
    }

    /**
     * updates the figures map
     */
    public final void updateFigurePositions()
    {
	// set figures to their start positions
	this.resetFigurePositions();
	// check if there is at least one move and if a move is selected
	if ( this.moveCount > 0 && this.selectedMove > -1 )
	{
	    String start;
	    String end;
	    Figure f;
	    Figure beaten;
	    // go through moves list from bottom to top
	    for ( int i = this.movesList.size() - 1; i >= this.selectedMove; i-- )
	    {
		start = this.movesList.get( i ).getStartField();
		end = this.movesList.get( i ).getEndField();
		f = this.figuresMap.remove( start );
		// TODO null pointer in spiel 1
		f.setPosition( end );

		// check if a figure exists on the end position, it becomes a beaten figure
		if ( this.figuresMap.containsKey( end ) )
		{
		    beaten = this.figuresMap.remove( end );
		    this.setBeatenPosition( beaten );
		    this.figuresMap.put( beaten.getPosition(), beaten );
		}
		// put figure on the end field
		this.figuresMap.put( end, f );

		// check if a castling move was done
		if ( this.movesList.get( i ).isCastling() )
		{
		    Figure tower;
		    switch ( end )
		    {
			case POS_B1:
			    tower = this.figuresMap.remove( POS_A1 );
			    tower.setPosition( POS_C1 );
			    this.figuresMap.put( POS_C1, tower );
			    break;
			case POS_G1:
			    tower = this.figuresMap.remove( POS_H1 );
			    tower.setPosition( POS_F1 );
			    this.figuresMap.put( POS_F1, tower );
			    break;
			case POS_B8:
			    tower = this.figuresMap.remove( POS_A8 );
			    tower.setPosition( POS_C8 );
			    this.figuresMap.put( POS_C8, tower );
			    break;
			case POS_G8:
			    tower = this.figuresMap.remove( POS_H8 );
			    tower.setPosition( POS_F8 );
			    this.figuresMap.put( POS_H8, tower );
			    break;
			default:
			    break;

		    }
		}

		// check if a en passant move was done
		if ( this.movesList.get( i ).isEnPassant() )
		{
		    if ( end.charAt( 1 ) == '3' )
		    {
			beaten = this.figuresMap.remove( end.replace( '3', '4' ) );
			this.setBeatenPosition( beaten );
			this.figuresMap.put( beaten.getPosition(), beaten );
		    }
		    if ( end.charAt( 1 ) == '6' )
		    {
			beaten = this.figuresMap.remove( end.replace( '6', '5' ) );
			this.setBeatenPosition( beaten );
			this.figuresMap.put( beaten.getPosition(), beaten );
		    }
		}

		// check pawn promotion
		if ( f.getBehav() instanceof Pawn )
		{
		    char cs = start.charAt( 1 );
		    char ce = end.charAt( 1 );

		    if ( ( cs == '2' && ce == '1' ) || ( cs == '7' && ce == '8' ) )
		    {
			f.setBehav( new Queen( f.getColor() ) );
		    }
		}

	    }
	}

	if ( this.selectedMove > 0 )
	{
	    resetFigureSelection();
	}

    }

    /**
     * resets the figure selection of this match
     */
    public final void resetFigureSelection()
    {
	if ( !this.selectedPosition.isEmpty() )
	{
	    this.figuresMap.get( this.selectedPosition ).setSelected( false );
	    this.selectedPosition = "";
	}
    }

    /**
     * sets position of this beaten figure
     * 
     * @param beaten
     *            the beaten figure
     */
    private void setBeatenPosition( Figure beaten )
    {
	beaten.setBeaten( true );
	boolean whiteFigure = beaten.isWhite();
	String help;

	if ( beaten.getBehav() instanceof Pawn )
	{
	    help = whiteFigure ? POS_BEATEN_WHITE_PAWN_1 : POS_BEATEN_BLACK_PAWN_1;
	    // find the next free number for a beaten pawn
	    for ( int i = 2; i < 9; i++ )
	    {
		if ( !this.figuresMap.containsKey( help ) )
		{
		    beaten.setPosition( help );
		    i = 9;
		}
		else
		{
		    help = help.substring( 0, 3 ).concat( String.valueOf( i ) );
		}
	    }
	}
	else if ( beaten.getBehav() instanceof Tower )
	{
	    help = whiteFigure ? POS_BEATEN_WHITE_TOWER_1 : POS_BEATEN_BLACK_TOWER_1;
	    // find the next free number for a beaten tower
	    if ( this.figuresMap.containsKey( help ) )
	    {
		help = help.substring( 0, 3 ).concat( "2" );
	    }
	    beaten.setPosition( help );
	}
	else if ( beaten.getBehav() instanceof Horse )
	{
	    help = whiteFigure ? POS_BEATEN_WHITE_HORSE_1 : POS_BEATEN_BLACK_HORSE_1;
	    // find the next free number for a beaten horse
	    if ( this.figuresMap.containsKey( help ) )
	    {
		help = help.substring( 0, 3 ).concat( "2" );
	    }
	    beaten.setPosition( help );
	}
	else if ( beaten.getBehav() instanceof Bishop )
	{
	    help = whiteFigure ? POS_BEATEN_WHITE_BISHOP_1 : POS_BEATEN_BLACK_BISHOP_1;
	    // find the next free number for a beaten bishop
	    if ( this.figuresMap.containsKey( help ) )
	    {
		help = help.substring( 0, 3 ).concat( "2" );
	    }
	    beaten.setPosition( help );
	}
	else if ( beaten.getBehav() instanceof Queen )
	{
	    // check if the beaten figure was a queen from the start, else it is a promoted pawn
	    String startPos = beaten.getStartPosition();
	    if ( startPos.equals( POS_D1 ) || startPos.equals( POS_D8 ) )
	    {
		help = whiteFigure ? POS_BEATEN_WHITE_QUEEN : POS_BEATEN_BLACK_QUEEN;
		beaten.setPosition( help );
	    }
	    else
	    {
		beaten.setBehav( new Pawn( beaten.getColor() ) );
		this.setBeatenPosition( beaten );
	    }

	}
    }

    /**
     * get the acting player of the move given by the moveNr
     * 
     * @param moveNr
     *            the number of move
     * @return the acting player of a move
     */
    public final String getActingPlayer( int moveNr )
    {
	String ret = ( moveNr % 2 == 1 ) ? this.getPlayerOne().getNick() : this.getPlayerTwo().getNick();
	return ret;
    }

    /**
     * repaints all figures
     * 
     * @param fieldsize
     *            the size of a field on the playing field
     * @param g
     *            the graphics object to draw on
     * @param color
     *            indicates which figures have to be drawn, -1 stands for active figures on the board, 0 are beaten
     *            white figures, 1 are beaten black figures
     */
    public final void repaintFigures( int fieldsize, Graphics g, int color )
    {
	Iterator<Figure> iterFigures = this.figuresMap.values().iterator();

	int pawns = 0;
	int towers = 0;
	int horses = 0;
	int bishops = 0;
	Figure f = null;
	while ( iterFigures.hasNext() )
	{
	    f = iterFigures.next();
	    if ( color < 0 && !f.isBeaten() )
	    {
		f.drawFigure( fieldsize, g, this.figuresMap, this.movesList );
	    }
	    else if ( f.isBeaten() )
	    {
		if ( ( color == Figure.WHITE && f.isWhite() ) || ( color == Figure.BLACK && f.isBlack() ) )
		{
		    if ( f.getPosition().matches( "B[WB]P." ) )
		    {
			pawns++;
		    }
		    else if ( f.getPosition().matches( "B[WB]T." ) )
		    {
			towers++;
		    }
		    else if ( f.getPosition().matches( "B[WB]H." ) )
		    {
			horses++;
		    }
		    else if ( f.getPosition().matches( "B[WB]B." ) )
		    {
			bishops++;
		    }
		    else
		    {
			// queen
			f.drawFigure( fieldsize, g, this.figuresMap, this.movesList );
		    }
		}
	    }
	}
	if ( color > -1 )
	{
	    String help = color == 0 ? "BW" : "BB";
	    String helpChar;
	    if ( pawns > 0 )
	    {
		helpChar = "P";
		if ( this.figuresMap.containsKey( help + helpChar + pawns ) )
		{
		    this.figuresMap.get( help + helpChar + pawns ).drawFigure( fieldsize, g, this.figuresMap,
			    this.movesList );
		}
	    }
	    if ( towers > 0 )
	    {
		helpChar = "T";
		if ( this.figuresMap.containsKey( help + helpChar + towers ) )
		{
		    this.figuresMap.get( help + helpChar + towers ).drawFigure( fieldsize, g, this.figuresMap,
			    this.movesList );
		}
	    }
	    if ( horses > 0 )
	    {
		helpChar = "H";
		if ( this.figuresMap.containsKey( help + helpChar + horses ) )
		{
		    this.figuresMap.get( help + helpChar + horses ).drawFigure( fieldsize, g, this.figuresMap,
			    this.movesList );
		}
	    }
	    if ( bishops > 0 )
	    {
		helpChar = "B";
		if ( this.figuresMap.containsKey( help + helpChar + bishops ) )
		{
		    this.figuresMap.get( help + helpChar + bishops ).drawFigure( fieldsize, g, this.figuresMap,
			    this.movesList );
		}
	    }
	}
    }

    /**
     * setter of movesList
     * 
     * @param movesList
     *            the movesList to set
     */
    public final void setMovesList( List<Move> movesList )
    {
	this.movesList = movesList;
    }

    /**
     * getter of id
     * 
     * @return the id
     */
    public final int getId()
    {
	return this.id;
    }

    /**
     * getter of playerOne
     * 
     * @return the playerOne
     */
    public final User getPlayerOne()
    {
	return this.playerOne;
    }

    /**
     * getter of playerTwo
     * 
     * @return the playerTwo
     */
    public final User getPlayerTwo()
    {
	return this.playerTwo;
    }

    /**
     * getter of winner
     * 
     * @return the winner
     */
    public final User getWinner()
    {
	return this.winner;
    }

    /**
     * getter of startDate
     * 
     * @return the startDate
     */
    public final String getStartDate()
    {
	return this.startDate;
    }

    /**
     * getter of moveCount
     * 
     * @return the moveCount
     */
    public final int getMoveCount()
    {
	return this.moveCount;
    }

    /**
     * getter of movesList
     * 
     * @return the movesList
     */
    public final List<Move> getMovesList()
    {
	return this.movesList;
    }

    /**
     * getter of remis
     * 
     * @return the remis
     */
    public final boolean isRemis()
    {
	return this.remis;
    }

    /**
     * getter of patt
     * 
     * @return the patt
     */
    public final boolean isPatt()
    {
	return this.patt;
    }

    /**
     * getter of selectedMove
     * 
     * @return the selectedMove
     */
    public final int getSelectedMove()
    {
	return this.selectedMove;
    }

    /**
     * setter of selectedMove
     * 
     * @param selectedMove
     *            the selectedMove to set
     */
    public final void setSelectedMove( int selectedMove )
    {
	this.selectedMove = selectedMove;
    }

    /**
     * getter of selectedPosition
     * 
     * @return the selectedPosition
     */
    public final String getSelectedPosition()
    {
	return this.selectedPosition;
    }

    /**
     * setter of selectedPosition
     * 
     * @param selectedPosition
     *            the selectedPosition to set
     */
    public final void setSelectedPosition( String selectedPosition )
    {
	this.selectedPosition = selectedPosition;
    }

}
